home *** CD-ROM | disk | FTP | other *** search
/ GameStar 2004 April / Gamestar_61_2004-04_dvdb.iso / DVDStar / Editace / hltp.exe / {app} / Applications / QuArK / plugins / mapmiteredge.py < prev    next >
Text File  |  2004-01-05  |  29KB  |  826 lines

  1. """   QuArK  -  Quake Army Knife Bezier shape makers
  2.  
  3.  
  4. """
  5.  
  6.  
  7. # THIS FILE IS PROTECTED BY THE GNU GENERAL PUBLIC LICENCE
  8. # FOUND IN FILE "COPYING.TXT"
  9. #
  10. #
  11. # $Header: /cvsroot/quark/runtime/plugins/mapmiteredge.py,v 1.11 2003/01/31 20:19:43 tiglari Exp $
  12. #
  13.  
  14. Info = {
  15.    "plug-in":       "Mitered Edge Plugin",
  16.    "desc":          "Make nice mitered edges where 2 faces join to make a surface",
  17.    "date":          "5 Sept 2001 -> 26 Jan 2003",
  18.    "author":        "tiglari",
  19.    "author e-mail": "tiglari@planetquake.com",
  20.    "quark":         "Quark 6.3" }
  21.  
  22.  
  23.  
  24. import quarkx
  25. from quarkpy.maputils import *
  26. import quarkpy.mapentities
  27. import quarkpy.qmovepal
  28. import quarkpy.mapduplicator
  29. import mapdups
  30. import mapextruder
  31. import tagging
  32.  
  33. #
  34. # These should go to maputils someday
  35. #
  36.  
  37. SMALL = .1
  38. SMALLER = .001
  39.  
  40. #
  41. # colinear from maptagside seems to be wrong, s/b replaced
  42. # by something like this when it's stabilized.
  43. #
  44. def colinear(list):
  45.     "first 2 should not be coincident"
  46.     if len(list) < 3:
  47.        return 1
  48.     norm = (list[1]-list[0]).normalized
  49.     v0 = list[0]
  50.     for v in list[2:]:
  51.         if abs(v0 - v)>SMALL:
  52.             norm2 = (v-v0).normalized
  53.             if abs(norm-norm2)<SMALLER:
  54.                 return 1
  55.             if abs(norm+norm2)<SMALLER:
  56.                 return 1
  57.         else:
  58.             return 1
  59.     return 0
  60.     
  61. def flatContainedWithin(list1, list2):
  62.     "every vertex in list1 lies on the face defined by list2"
  63.     len1 = len(list1)
  64.     cross = (list2[1]-list2[0])^(list2[2]-list2[1]).normalized
  65.     for i in range(len1):
  66.         v = list1[(i+1)%len1]-list1[i]
  67.         inward = cross^v
  68.         for w in list2:
  69.             if abs(list1[i]-w)<SMALL: #almost coincident == in
  70.                 continue
  71.             if (w-list1[i])*inward>0:
  72.                 return 0
  73.     return 1
  74.  
  75. def nextInList(list, current, incr=1):
  76.     return list[(current+incr)%len(list)]
  77.  
  78. def faceCenter(face, poly):
  79.     vtxes = face.verticesof(poly)
  80.     return reduce(lambda x,y:x+y, vtxes)/len(vtxes)
  81.  
  82. #
  83. # a perp to the edge made by vtx indexed i and the following vtx, in
  84. #  the face of the poly, pointing into the poly)
  85. #
  86. def perpFromVtx(i, face, poly):
  87.     vtxes = face.verticesof(poly)
  88.     vtx = vtxes[i]
  89.     vtx2 = vtxes[(i+1)%len(vtxes)]
  90.     return (face.normal^(vtx2-vtx)).normalized    
  91.  
  92. def parentnames(o):
  93. #    debug('p1')
  94.     name = o.name
  95.     while o.parent is not None:
  96. #        debug('loop')
  97.         o=o.parent
  98.         name = name+':'+o.name
  99.     return name
  100.  
  101. def overlapEdge(v1, v2, v3, v4):
  102. #    if abs((v1-v2).normalized+(v3-v4).normalized)<SMALL:
  103. #        debug('oppdir: '+`v1`+' '+`v2`+' '+`v3`+' '+`v4`)
  104. #    else:
  105. #        debug('samedir'+`v1`+' '+`v2`+' '+`v3`+' '+`v4`)
  106.     if abs(v1-v3)>SMALL:
  107.         if abs(v2-v4)>SMALL:
  108.             return 1
  109.         else:
  110.             return 0
  111.     diff = abs(v1-v2)
  112.     if math.fabs(diff-abs(v3-v1)-abs(v2-v3))<SMALL:
  113.         return 1
  114.     if math.fabs(diff-abs(v4-v1)-abs(v2-v4))<SMALL:
  115.         return 1
  116.     return 0
  117.         
  118. #
  119. # Assumes points are colinear
  120. #
  121. def overlapEdge(v1, v2, v3, v4):
  122.     if (v2-v1)*(v3-v1)>SMALL:
  123.         return 1
  124.     if (v1-v2)*(v4-v2)>SMALL:
  125.         return 1
  126.     return 0
  127.     
  128. #
  129. # Every 'facet' (face of a poly) of face2 is contained
  130. #   within some facet of face1
  131. #
  132. def facetsContained(face1, face2):
  133.     for vtxes2 in face2.vertices:
  134.         for vtxes1 in face1.vertices:
  135.             if not flatContainedWithin(vtxes2, vtxes1):
  136.                 return 0
  137.     return 1
  138.         
  139.  
  140. #
  141. # The two faces share an edge, and the shared vertices are
  142. #   appearing in opposite order on their lists (implies the
  143. #   two faces form a surface)
  144. #
  145. def findEdgePoints(f1, f2):
  146.     if f2 is None:
  147.         return 0
  148.     for poly1 in f1.faceof:
  149.         vtxlist1 = f1.verticesof(poly1)
  150.         for poly2 in f2. faceof:
  151.             vtxlist2 = f2.verticesof(poly2)
  152.             if poly1 is poly2:
  153.                 continue
  154.  
  155.             for i in range(len(vtxlist1)):
  156.                 nexti=nextInList(vtxlist1,i)
  157.                 for j in range(len(vtxlist2)):
  158.                     if colinear([vtxlist1[i], nexti, vtxlist2[j]]):
  159.                         prevj=nextInList(vtxlist2,j,-1)
  160.                         if colinear([vtxlist1[i], nexti, prevj]):
  161.                             if overlapEdge(vtxlist1[i], nexti, prevj, vtxlist2[j]):
  162.                                 return (poly1, i), (poly2, (j-1)%len(vtxlist2))
  163. #                            else:
  164. #                                debug('no overlap: '+parentnames(f1)+' '+parentnames(f2))
  165.                                 
  166.  
  167.  
  168. def findAdjoiningFace(poly, face, i):
  169.     vtxes = face.verticesof(poly)
  170.     vtx=vtxes[i]
  171.     for face2 in poly.faces:
  172.         if face2 is face:
  173.             continue
  174.         vtxes2 = face2.verticesof(poly)
  175.         for j in range(len(vtxes2)):
  176.             if abs(vtx - vtxes2[j])<SMALL:
  177.                 if abs(nextInList(vtxes,i)-nextInList(vtxes2,j,-1))<SMALL:
  178.                     return face2
  179.  
  180.  
  181. def findOppositeFace(poly, face):
  182.     for face2 in poly.faces:
  183.         if face2 is face:
  184.             continue
  185.         if abs(face.normal+face2.normal)<SMALL:
  186.             return face2
  187.  
  188. def findSharedVertex(face1, face2, poly):
  189.     for vtx in face1.verticesof(poly):
  190.         for vtx2 in face2.verticesof(poly):
  191.             if abs(vtx-vtx2)<SMALL:
  192.                 return vtx
  193.  
  194. def faceCenter(face, poly):
  195.     vtxes = face.verticesof(poly)
  196.     return reduce(lambda x,y:x+y, vtxes)/len(vtxes)
  197.  
  198.  
  199. #
  200. # for edgepoint format see return line of findEdgePoints
  201. # returns old, new list for substitution
  202. #
  203. def miterEdgeFaces(f1, f2, ((poly1, i1), (poly2, i2)), local_faces=[]):
  204.     face1 = findAdjoiningFace(poly1, f1, i1)
  205.     face2 = findAdjoiningFace(poly2, f2, i2) 
  206.     if face1 is None or face2 is None:
  207. #        debug('no adjoining')
  208.         return
  209.     #
  210.     # We're looking for paralell faces on the opposite side to
  211.     #   make a smooth join on that side if possible
  212.     #
  213.     oppface1 = findOppositeFace(poly1, f1)
  214.     oppface2 = findOppositeFace(poly2, f2)
  215.     vtxes = f1.verticesof(poly1)
  216.     vtx = vtxes[i1]
  217.     vtx2 = nextInList(vtxes,i1)
  218.     #
  219.     # get the 'extended faces' (backing onto the ones we're moving)
  220.     #
  221.     extendedfaces=[face1,face2]
  222.     quarkx.extendcoplanar(extendedfaces,local_faces)
  223.     ext2 = []
  224.     for face in extendedfaces:
  225.         if not(face is face1 or face is face2 or face in ext2) and (facetsContained(face, face1) or facetsContained(face, face2)):
  226.             ext2.append(face)
  227.     matched=0
  228.     #
  229.     # Try a technique which will line up the back faces nicely
  230.     #
  231.     
  232.     if oppface1 is not None and oppface2 is not None:
  233.         sharedvtx = findSharedVertex(face1, oppface1, poly1)
  234.         if sharedvtx is not None:
  235.             #
  236.             # find a point where the two opposite faces intersect
  237.             #
  238.             center=faceCenter(oppface1, poly1)
  239.             point = projectpointtoplane(center, sharedvtx-center,
  240.                      oppface2.dist*oppface2.normal,oppface2.normal)
  241.             oldlist = [face1, face2]+ext2
  242.             newlist=[]
  243.             for face in oldlist:
  244.                 newface=face.copy()
  245.                 diff2 = (vtx2-vtx).normalized
  246.                 norm = (diff2^(point-vtx)).normalized
  247.                 diff3 = norm^diff2
  248.                 newface.setthreepoints((vtx, vtx+128*diff2, vtx+128*diff3),0)
  249. #                newface["plan"] = 'A'
  250. #                if colinear([vtx, vtx2, point]):
  251. #                   debug('colinear: '+`vtx`+' '+`vtx2`+' '+`point`)
  252.                 newface['tex']=CaulkTexture()
  253.                 newlist.append(newface)
  254.             matched=1
  255.     #
  256.     # Well that won't work so Plan B
  257.     #
  258.     if not matched:
  259.         newface1 = face1.copy()
  260.         newface2 = face2.copy()
  261. #        newface1["plan"] = 'B1'
  262. #        newface2["plan"] = 'B2'
  263.         edge = (vtx2-vtx)
  264.         plane1 = edge^f1.normal
  265.         plane2 = edge^f2.normal
  266.         mitredir = mapextruder.make_edge(plane2, -plane1)
  267.         mat = matrix_rot_u2v(mitredir, plane1)
  268.         if mat is None:
  269.             return [face1, face2], [face1, face2]
  270.         newnormal = mat*f1.normal
  271.         newface1.distortion(newnormal,vtx)
  272.         newface2.distortion(-newnormal,vtx)
  273.         oldlist = [face1, face2]
  274.         newlist = [newface1, newface2]
  275.     for i in range(len(oldlist)):
  276.         p1, p2, p3 = oldlist[i].threepoints(0)
  277.         q1, q2, q3 = newlist[i].threepoints(0)
  278.         cross = ((p2-p1)^(p3-p2))*((q2-q1)^(q3-q1))
  279.         if cross<0:
  280.             newlist[i].setthreepoints((q1, q3, q2),0)
  281. #        elif cross==0:
  282. #            debug('zero cross')
  283.         newlist[i].rebuildall()
  284.     return oldlist, newlist
  285.  
  286. def miterEdge(f1, f2, edgepoints, editor):
  287.     oldlist, newlist = miterEdgeFaces(f1, f2, edgepoints, editor.Root.findallsubitems("",":f"))
  288.     undo = quarkx.action()
  289.     for i in range(len(oldlist)):
  290.         if newlist[i].normal*oldlist[i].normal<0:
  291.             newlist[i].swapsides()
  292.         undo.exchange(oldlist[i], newlist[i])
  293.     editor.ok(undo, "mitre edge")
  294. #    editor.layout.explorer.sellist=[f1]
  295.     editor.layout.explorer.sellist=newlist
  296.     
  297.  
  298.  
  299.  
  300. def mitrefacemenu(o, editor, oldmenu=quarkpy.mapentities.FaceType.menu.im_func):
  301.     "the new right-mouse menu for polys"
  302.     menu = oldmenu(o, editor)
  303.     
  304.     tagged = tagging.gettagged(editor)
  305.     
  306.     edgepoints = findEdgePoints(o, tagged)
  307.     
  308.     def miterEdgeClick(m, o=o, editor=editor, tagged=tagged, edgepoints=edgepoints):
  309.         miterEdge(o, tagged, edgepoints, editor)
  310.     
  311.     mitreitem = qmenu.item("Mitre Edge",miterEdgeClick)
  312.     
  313.     if edgepoints is None:
  314.         mitreitem.state=qmenu.disabled
  315.  
  316.     menu[:0] = [mitreitem]
  317.     
  318.     return menu
  319.     
  320. quarkpy.mapentities.FaceType.menu = mitrefacemenu
  321.  
  322.  
  323. def match_vertices(vtxes1, vtxes2):
  324.     len1 = len(vtxes1)
  325.     if len==len(vtxes2):
  326.         for i in range(len1):
  327.             for j in range(len2):
  328.                 if not vtxes1[i]-vtxes2[j]:
  329.                     for k in range(len1-1):
  330.                         if vtxes1[(i+k)%len1]-vtxes2[(j-k)%len1]:
  331.                             return 0
  332.                     else:
  333.                         return 1
  334.     return 0
  335.                      
  336. def makePrism(f, p, wallwidth):                        
  337.     walls = f.extrudeprism(p)
  338.     for wall in walls:
  339.         wall.texturename=f.texturename
  340.         f.shortname='wallside'
  341.     inner = f.copy()
  342. #    inner["ext_inner"]='1'
  343.     inner.shortname='inner'
  344.     inner.swapsides()
  345.     outer = f.copy()
  346. #    outer['ext_outer']='1'
  347.     outer.shortname='outer'
  348.     n = f.normal
  349.     n = n.normalized
  350.     outer.translate(abs(wallwidth)*n)
  351.     newp = quarkx.newobj(f.shortname+" wall:p")
  352.     #
  353.     # it's important than the inner one be first (to find
  354.     #   it quickly later), but something reverses the order (!!!)
  355.     #   so drop them in backwards
  356.     #
  357.     for face in [inner, outer] + walls:
  358.  #   for face in walls + [outer, inner]:
  359.         newp.appenditem(face)
  360.     for face in newp.faces:
  361.         for poly in face.faceof:
  362.            poly.rebuildall()
  363.     return newp
  364.                         
  365.  
  366. #
  367. # copied from plugins.csg, with modifications
  368. #
  369. def wallsFromPoly(plist, wallwidth=None):
  370.     import quarkpy.qmovepal
  371.     if wallwidth is None:
  372.         wallwidth, = quarkpy.qmovepal.readmpvalues("WallWidth", SS_MAP)
  373.     if wallwidth > 0:           #DECKER
  374.         wallwidth = -wallwidth  #DECKER
  375.     result = []
  376.     if wallwidth < 0:           #DECKER
  377.         for p in plist:
  378.             newg = quarkx.newobj(p.shortname+" group:g")
  379.             for f in p.faces:
  380.                 newp = makePrism(f, p, wallwidth)
  381.                 newg.appenditem(newp)
  382.             result.append(newg)
  383.         return result
  384.  
  385.  
  386. #
  387. # This code depends on all the games using Content Flag = 134217728 for detail
  388. #   Someday we'll need something more general
  389. #
  390. # Perhaps there should be a 'detail' attribute for polys, so that if a poly
  391. #   was set detail, then all its faces would be written that way in the map,
  392. #   as controlled by the gamecodes in the .exe, or something.
  393. #
  394. def setDetail(face):
  395.     contents = face['Contents']
  396.     if contents==None:
  397.         contents = 0
  398.     else:
  399.         contents = int(contents)
  400.     face['Contents'] = `contents | 134217728`
  401.  
  402.  
  403. def findMiterableFaces(faces):
  404.     fdict = {}
  405.     for fi1 in range(len(faces)):
  406.         face1 = faces[fi1]
  407.         for fi2 in range(fi1+1, len(faces)):
  408.             face2=faces[fi2]
  409. #            if face1.normal-face2.normal and face1.normal+face2.normal:
  410.             if abs(face1.normal-face2.normal)>SMALL and abs(face1.normal+face2.normal)>SMALL:
  411.                 edgepoints = findEdgePoints(face1, face2)
  412.                 if edgepoints is None:
  413.                     continue
  414.                 ((poly1, i1), (poly2, i2)) = edgepoints
  415.                 if poly1.type==":f" or poly2.type==":f":
  416.                     continue
  417.                 fdict[(face1, face2)] = edgepoints
  418.     return fdict
  419.  
  420. def setViewFlag(group, flag):
  421.     viewflags = group[';view']
  422.     if viewflags == None:
  423.         viewflags = 0
  424.     else:
  425.         viewflags = int(viewflags)
  426.     viewflags = viewflags | flag
  427.     group[';view'] = str(viewflags)
  428.  
  429.         
  430. def buildwallmakerimages(self, singleimage=None):
  431.         if not (self.dup["miter"] or self.dup["extrude"] or self.dup["solid"]):
  432.             return mapdups.DepthDuplicator.buildimages(self,singleimage)
  433.             
  434.         if singleimage is not None and singleimage>0:
  435.             return []
  436.         try:
  437.             self.readvalues()
  438.         except:
  439.             print "Note: Invalid Duplicator Specific/Args."
  440.             return
  441.         #
  442.         # The way all this works is roughly as follows:
  443.         #   The sourcelist is the list of things (polys, groups, whatever)
  444.         #      immediately contained within the duplicator
  445.         #   We make a list of copies of its elements, and add these to
  446.         #      a new group (wallgroup)
  447.         #   The we do all kinds of complicated stuff to the elements of the
  448.         #      list and their subitems, and since these things are all subitems
  449.         #      of wallgroup, the effects show up there.
  450.         #   Finally, the subitems of wallgroup are returned as the images, so
  451.         #      that the organization of the input is preserved in the output,
  452.         #      even tho faces have been replaced by brushes, etc. etc.
  453.         #   One elaboration on this is that in caulkull wallmakers, the return
  454.         #      list is two groups, detail and hull, each preserving the structure
  455.         #      of the input (but with 'plugs' getting carved out of the detail
  456.         #      but not the hull)
  457.         #
  458.  
  459.         sourcecopy=[]
  460.         for item in self.sourcelist():
  461.             sourcecopy.append(item.copy())
  462.         #
  463.         # quick exit for fast redesign, but not when images are
  464.         #   being dissociated
  465.         #
  466.         if self.dup["solid"]=='1' and singleimage is None:
  467. #            for item in sourcecopy:
  468. #                for face in item.findallsubitems("",":f"):
  469. #                   face['inverse']='1'
  470.             return sourcecopy
  471.         if singleimage is not None:
  472.             sourcegroup = quarkx.newobj('source:g')
  473.             for poly in sourcecopy:
  474.                 sourcegroup.appenditem(poly.copy())
  475.             setViewFlag(sourcegroup,VF_HIDDEN | VF_IGNORETOBUILDMAP | VF_CANTSELECT | VF_HIDEON3DVIEW)
  476.             for spec in self.dup.dictspec.keys():
  477.                 sourcegroup[spec]=self.dup[spec]
  478.         #
  479.         # we won't return this group as a value, but we need it to use
  480.         #  our trick for preserving the tree structure.
  481.         #
  482.         wallgroup = quarkx.newobj("wallgroup:g")
  483.         for item in sourcecopy:
  484.             wallgroup.appenditem(item)
  485.         caulkhull = self.dup["caulkhull"]
  486.         caulktexture = CaulkTexture()
  487.         #
  488.         # perhaps we should scan the tree and build these lists then, rather
  489.         #  than make a list and sift it.
  490.         #
  491.         allpolys = wallgroup.findallsubitems("",":p")
  492.         #
  493.         # The ones that will have walls extruded from them
  494.         #
  495.         polys = []
  496.         #
  497.         # the ones that make holes
  498.         #
  499.         negatives = []
  500.         plugs = []
  501.         for item in allpolys:
  502.             if  item["plug"]=='1':
  503.                 pass
  504.             elif item["neg"]=='1':
  505.                 negatives.append(item)
  506.             else:
  507.                 polys.append(item)
  508.         depth=int(self.dup["depth"])
  509.         #
  510.         # make the walls
  511.         #
  512.         wallgroups = map(lambda item:item.subitems, wallsFromPoly(polys, depth))
  513.         #
  514.         # cut away the portals.  it would probably be better to write code to
  515.         #  do this directly on the basis of overlapping faces, rather than
  516.         #  going thru poly subtraction, but me too stupid and lazy.
  517.         #
  518.         for i in range(len(polys)):
  519.             walls = wallgroups[i]
  520.             newwalls = []
  521.             for wall in walls:
  522.                 wallbits = [wall]
  523.                 for j in range(len(polys)):
  524.                     if i==j:
  525.                         continue
  526.                     if wall.intersects(polys[j]):
  527.                         inner = wall.subitems[0]
  528.                         innerp=inner.threepoints(0)[0]
  529.                         poly = polys[j].copy()
  530.                         for face in poly.faces:
  531.                             if abs(inner.normal-face.normal)<SMALL and math.fabs((face.dist*face.normal-innerp)*inner.normal)<SMALL:
  532.                                 face.swapsides()
  533.                                 brush=makePrism(face,poly,self.depth)
  534.                                 for bface in brush.subitems[:2]:
  535.                                     bface.translate(10*bface.normal)
  536.                                 wallbits=brush.subtractfrom(wallbits)
  537.                                 #
  538.                                 # face-order gets messed up by subtraction
  539.                                 #
  540.                                 break
  541.                 newwalls = newwalls+wallbits   
  542.             for hole in negatives:
  543.                 newwalls=hole.subtractfrom(newwalls)
  544.             newgroup=quarkx.newobj(polys[i].shortname+':g')
  545.             parent = polys[i].parent
  546.             parent.removeitem(polys[i])
  547.             parent.appenditem(newgroup)
  548.             for wall in newwalls:
  549.                 newgroup.appenditem(wall)
  550.   #      faces = filter(lambda f:f["ext_inner"]=='1', wallgroup.findallsubitems("",":f"))
  551.         faces = wallgroup.findallsubitems("inner",":f")
  552.         replacedict = {}
  553.         donefaces = {}
  554.         if self.dup["miter"]=='1':
  555.             miterfaces = findMiterableFaces(faces)
  556.             #
  557.             # miterfaces is a dictionary indexed by pairs of faces.  Each
  558.             #  pairset occurs once.  The order in which the members of the
  559.             #  pairset is listed in the pair is arbitrary
  560.             #
  561. #            debug('%d miterfaces'%len(miterfaces.keys()))
  562.             for (face1, face2) in miterfaces.keys():
  563.                 #
  564.                 # this is the format of the entries in miterfaces, poly
  565.                 # being the poly of the face, i the index of the first
  566.                 # vertex where the two faces adjoin
  567.                 #
  568.                 # ((poly1, i1), (poly2, i2)) = miterfaces[face1][face2]
  569.                 #
  570. #                debug('face 1 %s'%parentnames(face1))
  571. #                debug('face 2 %s'%parentnames(face2))
  572.                 edgepoints = miterfaces[(face1, face2)]
  573.                 #
  574.                 # old is a list of faces in the map which need to be mitered,
  575.                 #  first the miterable edge adjoining face1, then the one
  576.                 #  adjoining face2
  577.                 #
  578.                 old, new = miterEdgeFaces(face1, face2, edgepoints)
  579.                 def checkface(i, face, (poly, vi), donefaces=donefaces, new=new):
  580.                     if donefaces.has_key((face,vi)):
  581.                         #
  582.                         # the new new should be the one that makes
  583.                         #  the most acute angle to face
  584.                         #
  585.                         vtxes = face.verticesof(poly)
  586.                         vtx = vtxes[vi]
  587.                         vtx2 = vtxes[(vi+1)%len(vtxes)]
  588.                         xaxis = (face.normal^(vtx2-vtx)).normalized
  589.                         yaxis = face.normal
  590.                         def getangle(newface, span=(vtx-vtx2),
  591.                                xaxis=(vtx2-vtx^face.normal).normalized,
  592.                                yaxis=face.normal):
  593.                             newvec = (newface.normal^span).normalized
  594.                             return math.atan2(yaxis*newvec, xaxis*newvec)
  595.                         newang = getangle(new[i])
  596.                         oldang = getangle(donefaces[(face,vi)])
  597. #                        debug('angles %.2f, %.2f'%(newang/deg2rad,oldang/deg2rad))
  598.                         if newang<oldang: # don't do this replacement
  599.                             return 0
  600.                     donefaces[(face, vi)]=new[i]            
  601.                     return 1
  602.                 
  603.                 faces = (face1, face2)
  604.                 #
  605.                 # Now we try to specify the replacements we want, avoiding
  606.                 #  mitered faces that will stick in to the volume
  607.                 #
  608.                 for i in range(len(old)):
  609.                     if checkface(i, faces[i], edgepoints[i]):
  610.                         replacedict[old[i]]=new[i]
  611.  
  612.             #
  613.             # seems awkward but doesnt work other wayz
  614.             #
  615.             polylist=wallgroup.findallsubitems("",":p")
  616.             for poly in polylist:
  617.                 for face in poly.subitems:
  618.                     if replacedict.has_key(face):
  619.                         poly.removeitem(face)
  620.                         newface = replacedict[face]
  621.                         poly.appenditem(newface)
  622.                         poly.rebuildall()
  623.                         if poly.broken:
  624.                             newface.swapsides()
  625.                         poly.rebuildall()
  626.                         if poly.broken:
  627.                             debug('fuck, still busted')
  628.  
  629.             #
  630.             # Now generate the caulk hull if wanted
  631.             #
  632.             if caulkhull!=None:
  633.                 caulkdepth = int(caulkhull)
  634.                 if self.dup['caulksetback']!=None:
  635.                     caulksetback = eval(self.dup['caulksetback'])
  636.                 else:
  637.                     caulksetback = 0
  638.                 #
  639.                 # This depends on findallsubitems producing the same
  640.                 #   order in the list from the same structure of the
  641.                 #   item (seems to work).  wallgroup will be the caulk hull,
  642.                 #   wallgroup_detail the detail
  643.                 #
  644.                 wallgroup_detail = wallgroup.copy()
  645.                 polylist_detail=wallgroup_detail.findallsubitems("",":p")
  646.                 #
  647.                 # using indexes so can access both lists
  648.                 #
  649.                 for i in range(len(polylist)):
  650.                     poly = polylist[i]
  651.                     poly_detail = polylist_detail[i]
  652.                     #
  653.                     # remove the plugs from the caulkhull
  654.                     #
  655.                     if poly['plug']=='1':
  656.                         poly.parent.removeitem(poly)
  657.                         continue
  658.                     #
  659.                     # get the inner face ('.faces' attribute doesn't preserve
  660.                     #  subitem order; shared faces won't arise in this context
  661.                     #
  662.                     inner = poly.findname("inner:f")
  663.                     #
  664.                     # if it's already caulked, delete it from the detail
  665.                     #
  666.                     if inner['tex']==caulktexture:
  667.                         poly_detail.parent.removeitem(poly_detail)
  668.                     inner['tex'] = caulktexture
  669.                     if caulksetback:
  670.                         inner.translate(-caulksetback*inner.normal)
  671.                     outer = poly.findname("outer:f")
  672.                     outer.translate((caulkdepth-depth)*outer.normal)
  673.                     outer["tex"] = CaulkTexture()
  674.                     if not poly_detail.broken:
  675.                         for face in poly_detail.subitems:
  676.                             #
  677.                             # set detail flag here
  678.                             #
  679.                             if face.shortname=='outer':
  680.                                 face['tex']=caulktexture
  681.                             setDetail(face)
  682.                 plugs = []
  683.                 for item in polylist_detail:
  684.                     if item['plug']=='1':
  685.                         plugs.append(item)
  686.                 for plug in plugs:
  687.                    negplug = plug.copy()
  688.                    negplug['neg']=1
  689.                    for poly in polylist_detail:
  690. #                       if poly==plug:
  691. #                          debug('poly is plug')
  692.                        if poly!=plug and plug.intersects(poly):
  693.                            polybits = [poly]
  694.                            polybits = negplug.subtractfrom(polybits)
  695.                            polygroup = quarkx.newobj(poly.shortname+':g')
  696.                            for bit in polybits:
  697.                                polygroup.appenditem(bit.copy())
  698.                                
  699.                            parent = poly.parent
  700.                            parent.removeitem(poly)
  701.                            parent.appenditem(polygroup)
  702.                    #
  703.                    # negative plugs just cut the hole, 
  704.                    #
  705.                    if plug['neg']!=1:
  706.                       parent.appenditem(plug.copy())
  707.         
  708.         
  709.         #
  710.         # if relevant, build the hull and detail groups
  711.         #
  712.         if self.dup['miter']=='1' and self.dup['caulkhull']!=None:
  713.             hull = quarkx.newobj('hull:g')
  714.             for item in wallgroup.subitems:
  715.                 wallgroup.removeitem(item)
  716.                 hull.appenditem(item)
  717.             detail = quarkx.newobj('detail:g')
  718.             for item in wallgroup_detail.subitems:
  719.                 wallgroup_detail.removeitem(item)
  720.                 detail.appenditem(item) 
  721.             if self.dup['showcaulk']=='1':
  722.                 setViewFlag(detail,VF_HIDEON3DVIEW)
  723.             else:
  724.                 setViewFlag(hull,VF_HIDEON3DVIEW)
  725.             setViewFlag(hull,VF_CANTSELECT)
  726.             setViewFlag(detail,VF_CANTSELECT)
  727.             list = [detail, hull]
  728.         #
  729.         # otherwise output
  730.         #
  731.         else:
  732.             output = quarkx.newobj('output:g')
  733.             setViewFlag(output,VF_CANTSELECT)
  734.             for item in wallgroup.subitems:
  735.                 wallgroup.removeitem(item)
  736.                 output.appenditem(item)
  737.             list = [output]
  738.         if singleimage is not None:
  739.             list.append(sourcegroup)
  740.         return list
  741.  
  742.  
  743. mapdups.WallMaker.buildimages = buildwallmakerimages
  744.         
  745.  
  746.  
  747. def groupmenu(o, editor, oldmenu=quarkpy.mapentities.GroupType.menu.im_func):
  748.     menu = oldmenu(o, editor)
  749.     sourcegroup = o.findname("source:g")
  750.     if sourcegroup is not None and sourcegroup['macro']=='wall maker':
  751.     
  752.         def revertClick(m, o=o,editor=editor,source=sourcegroup):
  753.             #
  754.             # reversion adds ' (1)' to the name, so we drop the
  755.             #   last four characters
  756.             #
  757.             newdup = quarkx.newobj(o.shortname[:-4]+':d')
  758.             newdup.copyalldata(source)
  759.             #
  760.             # cancel the hiding/cantselect flags of the data group
  761.             #
  762.             newdup[';view'] = None
  763.             undo=quarkx.action()
  764.             undo.exchange(o, newdup)
  765.             editor.ok(undo,'revert to wall maker')
  766.         
  767.         revertitem = qmenu.item('Revert Walls to Duplicator',revertClick,"|This group was created from a wall maker duplicator.\nThis menu item will restore the original ducplicator and its data,\nfor convenient editing")
  768.         menu = [revertitem]+menu   
  769.  
  770.     return menu
  771.  
  772. quarkpy.mapentities.GroupType.menu = groupmenu
  773.         
  774.     
  775.     
  776.  
  777.  
  778. #
  779. #quarkpy.mapduplicator.DupCodes.update({
  780. #  "new wall maker":       NewWallMaker,})
  781.   
  782.  
  783.  
  784.  
  785. #
  786. # $Log: mapmiteredge.py,v $
  787. # Revision 1.11  2003/01/31 20:19:43  tiglari
  788. # improvements, tsx from rel63a branch (post 630 release)
  789. #
  790. # Revision 1.6.6.5  2003/01/14 20:41:35  tiglari
  791. # remove extra copy of plug
  792. #
  793. # Revision 1.6.6.4  2003/01/11 21:27:54  tiglari
  794. # add 'plugs' to extruded walls
  795. #
  796. # Revision 1.6.6.3  2003/01/04 04:35:50  tiglari
  797. # remove swapsides_leavetex() optimization - it makes bad texture scales
  798. #  that create problems for some build tools
  799. #
  800. # Revision 1.6.6.2  2003/01/01 05:09:14  tiglari
  801. # reinstate swapsides_leavetex as an optimization
  802. #
  803. # Revision 1.6.6.1  2002/05/18 22:45:55  tiglari
  804. # remove debug statements
  805. #
  806. # Revision 1.6  2001/10/08 10:42:47  tiglari
  807. # solid mode added, group structure preserved when dissociated
  808. #
  809. # Revision 1.5  2001/10/07 22:34:53  tiglari
  810. # negative polys dig hole in walls, extrude mode, colinear to maputils
  811. #
  812. # Revision 1.4  2001/10/07 00:43:59  tiglari
  813. # caulk-exposing bug supposedly fixed (by a considerable reorganizing
  814. # of the mitering process, using a dictionary instead of successive list
  815. # replacements)
  816. #
  817. # Revision 1.3  2001/10/01 06:20:31  tiglari
  818. # improve overlapping & colinear edge detection
  819. #
  820. # Revision 1.2  2001/09/23 08:56:57  tiglari
  821. # oops, replace 'bevel' with 'miter' in wallmaker stuff
  822. #
  823. # Revision 1.1  2001/09/23 07:00:34  tiglari
  824. # mitered edges for wall maker duplicator
  825. #
  826. #